/*-------------------------------------------------------------------------
 *
 * elog.h
 *	  POSTGRES错误报告/日志定义。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/utils/elog.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef ELOG_H
#define ELOG_H

#include <setjmp.h>

/* 错误级别代码 */
#define DEBUG5		10			/* 调试消息，按
								 * 递减详细程度分类。 */
#define DEBUG4		11
#define DEBUG3		12
#define DEBUG2		13
#define DEBUG1		14			/* 用于 GUC debug_* 变量 */
#define LOG			15			/* 服务器操作消息；默认情况下仅发送到服务器日志。 */
#define LOG_SERVER_ONLY 16		/* 与服务器报告的 LOG 相同，但从未发送到客户端。 */
#define COMMERROR	LOG_SERVER_ONLY /* 客户端通信问题；与服务器报告的 LOG 相同，但从未发送到客户端。 */
#define INFO		17			/* 用户特别请求的消息（例如 VACUUM VERBOSE 输出）；总是发送到客户端，无论 client_min_messages 如何，但默认情况下不发送到服务器日志。 */
#define NOTICE		18			/* 关于查询操作的有用消息；默认情况下发送到客户端，而不发送到服务器日志。 */
#define WARNING		19			/* 警告。NOTICE 用于预期的消息，如由 SERIAL 隐式创建的序列。WARNING 用于意外的消息。 */
#define PGWARNING	19			/* 必须等于 WARNING；请参见下面的注释。 */
#define WARNING_CLIENT_ONLY	20	/* 警告将照常发送给客户端，但永远不会发送到服务器日志。 */
#define ERROR		21			/* 用户错误 - 中止事务；返回到已知状态 */
#define PGERROR		21			/* 必须等于 ERROR；请参见下面的注释。 */
#define FATAL		22			/* 致命错误 - 中止进程 */
#define PANIC		23			/* 与我一起关闭其他后端 */

/*
 * 注：替代名称 PGWARNING 和 PGERROR 对处理
 * 其他定义了 WARNING 和/或 ERROR 的第三方头文件很有用。
 * 例如，可以在包含这样的头文件后将 ERROR 重新定义为 PGERROR。
 */


/* 用于紧凑表示 SQLSTATE 字符串的宏 */
#define PGSIXBIT(ch)	(((ch) - '0') & 0x3F)
#define PGUNSIXBIT(val) (((val) & 0x3F) + '0')

#define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5)	\
	(PGSIXBIT(ch1) + (PGSIXBIT(ch2) << 6) + (PGSIXBIT(ch3) << 12) + \
	 (PGSIXBIT(ch4) << 18) + (PGSIXBIT(ch5) << 24))

/* 这些宏依赖于 '0' 在 PGSIXBIT 中变为零的事实 */
#define ERRCODE_TO_CATEGORY(ec)  ((ec) & ((1 << 12) - 1))
#define ERRCODE_IS_CATEGORY(ec)  (((ec) & ~((1 << 12) - 1)) == 0)

/* 错误的 SQLSTATE 代码在单独的文件中定义 */
#include "utils/errcodes.h"

/*
 * 提供一种方法来防止 "errno" 在
 * elog() 或 ereport() 调用中被意外使用。由于我们知道某些操作系统
 * 将 errno 定义为与函数调用有关的东西，因此我们将在
 * 本地作用域中放置一个与该函数同名的局部变量，以强制产生
 * 编译错误。在不以该方式定义 errno 的平台上，不会发生任何
 * 事情，所以我们不会收到警告……但只要它发生在某些流行平台上
 * 我们就可以接受。
 */
#if defined(errno) && defined(__linux__)
#define pg_prevent_errno_in_scope() int __errno_location pg_attribute_unused()
#elif defined(errno) && (defined(__darwin__) || defined(__freebsd__))
#define pg_prevent_errno_in_scope() int __error pg_attribute_unused()
#else
#define pg_prevent_errno_in_scope()
#endif


/*----------
 * 新样式的错误报告 API：以这种方式使用：
 *		ereport(ERROR,
 *				errcode(ERRCODE_UNDEFINED_CURSOR),
 *				errmsg("portal \"%s\" not found", stmt->portalname),
 *				... 其他 errxxx() 字段根据需要 ...);
 *
 * 错误级别是必需的，主要错误消息 (errmsg 或 errmsg_internal) 也是必需的。
 * 其他都是可选的。如果 elevel 为 ERROR 或更高，errcode() 默认
 * 设为 ERRCODE_INTERNAL_ERROR；如果 elevel 为 WARNING，则为
 * ERRCODE_WARNING；如果 elevel 为 NOTICE 或更低，则为
 * ERRCODE_SUCCESSFUL_COMPLETION。
 *
 * 在 Postgres v12 之前，辅助函数调用列表周围需要额外的括号；
 * 现在这是可选的。
 *
 * ereport_domain() 允许指定消息域，以供希望使用与后端不同消息目录的模块。
 * 为了避免每个 .o 文件都有一个默认文本域的副本，我们在这里将其定义为 NULL，
 * 并让 errstart 插入默认文本域。模块可以直接使用
 * ereport_domain()，或者最好是可以重写 TEXTDOMAIN 宏。
 *
 * 当 __builtin_constant_p 可用且 elevel >= ERROR 时，我们将调用
 * errstart_cold() 而不是 errstart()。这个版本的函数带有
 * pg_attribute_cold 标记，这将促使支持的编译器生成
 * 更针对非 ERROR 情况下优化的代码。因为我们在条件中使用了
 * __builtin_constant_p()，当 elevel 不是一个
 * 编译时常量，或者如果是，但小于 ERROR，编译器就不需要为这个分支
 * 生成任何代码。它可以简单地无条件调用 errstart()。
 *
 * 如果 elevel >= ERROR，则调用不会返回；我们尝试通过
 * pg_unreachable() 通知编译器这一点。然而，除非编译器将
 * elevel 视为编译时常量，否则没有获得有用的优化效果，否则
 * 我们只是添加了代码膨胀。所以，如果 __builtin_constant_p 可用，
 * 使用它可以使得对非常量情况下第二个 if() 完全消失。
 * 我们避免使用局部变量，因为这不是必要的，并且会阻止 gcc 在
 * optlevel -O0 下做出不可达性推断。
 *----------
 */
#ifdef HAVE__BUILTIN_CONSTANT_P
#define ereport_domain(elevel, domain, ...)	\
	do { \
		pg_prevent_errno_in_scope(); \
		if (__builtin_constant_p(elevel) && (elevel) >= ERROR ? \
			errstart_cold(elevel, domain) : \
			errstart(elevel, domain)) \
			__VA_ARGS__, errfinish(__FILE__, __LINE__, PG_FUNCNAME_MACRO); \
		if (__builtin_constant_p(elevel) && (elevel) >= ERROR) \
			pg_unreachable(); \
	} while(0)
#else							/* !HAVE__BUILTIN_CONSTANT_P */
#define ereport_domain(elevel, domain, ...)	\
	do { \
		const int elevel_ = (elevel); \
		pg_prevent_errno_in_scope(); \
		if (errstart(elevel_, domain)) \
			__VA_ARGS__, errfinish(__FILE__, __LINE__, PG_FUNCNAME_MACRO); \
		if (elevel_ >= ERROR) \
			pg_unreachable(); \
	} while(0)
#endif							/* HAVE__BUILTIN_CONSTANT_P */

#define ereport(elevel, ...)	\
	ereport_domain(elevel, TEXTDOMAIN, __VA_ARGS__)

#define TEXTDOMAIN NULL

extern bool message_level_is_interesting(int elevel);

extern bool errstart(int elevel, const char *domain);
extern pg_attribute_cold bool errstart_cold(int elevel, const char *domain);
extern void errfinish(const char *filename, int lineno, const char *funcname);

extern int	errcode(int sqlerrcode);

extern int	errcode_for_file_access(void);
extern int	errcode_for_socket_access(void);

extern int	errmsg(const char *fmt,...) pg_attribute_printf(1, 2);
extern int	errmsg_internal(const char *fmt,...) pg_attribute_printf(1, 2);

extern int	errmsg_plural(const char *fmt_singular, const char *fmt_plural,
						  unsigned long n,...) pg_attribute_printf(1, 4) pg_attribute_printf(2, 4);

extern int	errdetail(const char *fmt,...) pg_attribute_printf(1, 2);
extern int	errdetail_internal(const char *fmt,...) pg_attribute_printf(1, 2);

extern int	errdetail_log(const char *fmt,...) pg_attribute_printf(1, 2);

extern int	errdetail_log_plural(const char *fmt_singular,
								 const char *fmt_plural,
								 unsigned long n,...) pg_attribute_printf(1, 4) pg_attribute_printf(2, 4);

extern int	errdetail_plural(const char *fmt_singular, const char *fmt_plural,
							 unsigned long n,...) pg_attribute_printf(1, 4) pg_attribute_printf(2, 4);

extern int	errhint(const char *fmt,...) pg_attribute_printf(1, 2);

extern int	errhint_plural(const char *fmt_singular, const char *fmt_plural,
						   unsigned long n,...) pg_attribute_printf(1, 4) pg_attribute_printf(2, 4);

/*
 * errcontext() 通常在错误上下文回调函数中调用，而不是在
 * ereport() 调用中。回调函数可以与 ereport() 调用在不同的模块中，
 * 因此传递给 errstart() 的消息域通常不是翻译上下文消息的正确域。
 * set_errcontext_domain() 首先设置要使用的域，而
 * errcontext_msg() 则传递实际消息。
 */
#define errcontext	set_errcontext_domain(TEXTDOMAIN),	errcontext_msg

extern int	set_errcontext_domain(const char *domain);

extern int	errcontext_msg(const char *fmt,...) pg_attribute_printf(1, 2);

extern int	errhidestmt(bool hide_stmt);
extern int	errhidecontext(bool hide_ctx);

extern int	errbacktrace(void);

extern int	errposition(int cursorpos);

extern int	internalerrposition(int cursorpos);
extern int	internalerrquery(const char *query);

extern int	err_generic_string(int field, const char *str);

extern int	geterrcode(void);
extern int	geterrposition(void);
extern int	getinternalerrposition(void);



/*----------
 * 旧式错误报告API：使用方法如下：
 *		elog(ERROR, "portal \"%s\" not found", stmt->portalname);
 *----------
 */
#define elog(elevel, ...)  \
	ereport(elevel, errmsg_internal(__VA_ARGS__))


/* 支持在ereport()调用中单独构造错误字符串 */

extern void pre_format_elog_string(int errnumber, const char *domain);
extern char *format_elog_string(const char *fmt,...) pg_attribute_printf(1, 2);


/* 支持将上下文信息附加到错误报告上 */

typedef struct ErrorContextCallback
{
	struct ErrorContextCallback *previous;
	void		(*callback) (void *arg);
	void	   *arg;
} ErrorContextCallback;

extern PGDLLIMPORT ErrorContextCallback *error_context_stack;


/*----------
 * 捕获ereport(ERROR)退出的API。使用这些宏如下：
 *
 *		PG_TRY();
 *		{
 *			... 可能抛出ereport(ERROR)的代码 ...
 *		}
 *		PG_CATCH();
 *		{
 *			... 错误恢复代码 ...
 *		}
 *		PG_END_TRY();
 *
 * （大括号实际上不是必需的，但推荐使用，以便
 * pgindent能很好地缩进构造。）错误恢复代码
 * 可以执行PG_RE_THROW以向外传播错误，或者执行
 * （子）事务回滚。不这样做可能会使系统在后续处理过程中处于不一致状态。
 *
 * 对于错误恢复代码和正常代码路径中的清理代码相同的常见情况，
 * 可以使用以下内容：
 *
 *		PG_TRY();
 *		{
 *			... 可能抛出ereport(ERROR)的代码 ...
 *		}
 *		PG_FINALLY();
 *		{
 *			... 清理代码 ...
 *		}
 *      PG_END_TRY();
 *
 * 清理代码在两种情况下都会被执行，任何错误都会在之后重新抛出。
 *
 * 不能在同一个PG_TRY()/PG_END_TRY()块中同时使用PG_CATCH()和PG_FINALLY()。
 *
 * 注意：虽然系统会正确传播恢复部分中发生的任何新ereport(ERROR)，
 * 但对于这一点的有效层数有限。最好保持错误恢复部分足够简单，以至于
 * 它不能产生任何新错误，至少在弹出错误堆栈之前不能。
 *
 * 注意：ereport(FATAL)不会被这个结构捕获；控制将直接通过proc_exit()退出。
 * 因此，不要将任何非进程本地资源的清理放入错误恢复部分，
 * 至少在没有考虑到ereport(FATAL)发生时会发生什么的情况下不要这样做。
 * storage/ipc.h提供的PG_ENSURE_ERROR_CLEANUP宏在这种情况下可能会有帮助。
 *
 * 注意：如果包含PG_TRY的函数中的局部变量在PG_TRY部分被修改
 * 并在PG_CATCH部分中使用，那么该变量必须声明为“volatile”以符合POSIX标准。
 * 这不是小题大做；我们见过编译器在未标记这样的变量时错误地优化代码
 * 的错误。请注意，gcc的-Wclobbered警告几乎完全无用，无法捕获这样的疏忽。
 *----------
 */
#define PG_TRY()  \
	do { \
		sigjmp_buf *_save_exception_stack = PG_exception_stack; \
		ErrorContextCallback *_save_context_stack = error_context_stack; \
		sigjmp_buf _local_sigjmp_buf; \
		bool _do_rethrow = false; \
		if (sigsetjmp(_local_sigjmp_buf, 0) == 0) \
		{ \
			PG_exception_stack = &_local_sigjmp_buf

#define PG_CATCH()	\
		} \
		else \
		{ \
			PG_exception_stack = _save_exception_stack; \
			error_context_stack = _save_context_stack

#define PG_FINALLY() \
		} \
		else \
			_do_rethrow = true; \
		{ \
			PG_exception_stack = _save_exception_stack; \
			error_context_stack = _save_context_stack

#define PG_END_TRY()  \
		} \
		if (_do_rethrow) \
				PG_RE_THROW(); \
		PG_exception_stack = _save_exception_stack; \
		error_context_stack = _save_context_stack; \
	} while (0)

/*
 * 一些编译器理解pg_attribute_noreturn(); 对于其他编译器，
 * 插入pg_unreachable()以便编译器明白要点。
 */
#ifdef HAVE_PG_ATTRIBUTE_NORETURN
#define PG_RE_THROW()  \
	pg_re_throw()
#else
#define PG_RE_THROW()  \
	(pg_re_throw(), pg_unreachable())
#endif

extern PGDLLIMPORT sigjmp_buf *PG_exception_stack;


/* 错误处理程序可能想要使用的东西 */

/*
 * ErrorData 保存在一次 ereport() 循环中积累的数据。
 * 任何非 NULL 指针必须指向 palloc 分配的数据。
 * （const 指针是一个例外；我们假设它们指向的是不可释放的
 * 常量字符串。）
 */
typedef struct ErrorData
{
	int			elevel;			/* 错误级别 */
	bool		output_to_server;	/* 是否将报告记录到服务器日志？ */
	bool		output_to_client;	/* 是否将报告发送给客户端？ */
	bool		hide_stmt;		/* true 以防止 STATEMENT: 包含 */
	bool		hide_ctx;		/* true 以防止 CONTEXT: 包含 */
	const char *filename;		/* ereport() 调用的 __FILE__ */
	int			lineno;			/* ereport() 调用的 __LINE__ */
	const char *funcname;		/* ereport() 调用的 __func__ */
	const char *domain;			/* 消息域 */
	const char *context_domain; /* 上下文消息的消息域 */
	int			sqlerrcode;		/* 编码的 ERRSTATE */
	char	   *message;		/* 主要错误消息（已翻译） */
	char	   *detail;			/* 详细错误消息 */
	char	   *detail_log;		/* 仅服务器日志的详细错误消息 */
	char	   *hint;			/* 提示消息 */
	char	   *context;		/* 上下文消息 */
	char	   *backtrace;		/* 回溯 */
	const char *message_id;		/* 主要消息的 id（原始字符串） */
	char	   *schema_name;	/* 架构的名称 */
	char	   *table_name;		/* 表名 */
	char	   *column_name;	/* 列的名称 */
	char	   *datatype_name;	/* 数据类型名称 */
	char	   *constraint_name;	/* 约束的名称 */
	int			cursorpos;		/* 查询字符串中的游标索引 */
	int			internalpos;	/* 内部查询中的游标索引 */
	char	   *internalquery;	/* 内部生成查询的文本 */
	int			saved_errno;	/* 进入时的 errno */

	/* 包含相关非常量字符串的上下文 */
	struct MemoryContextData *assoc_context;
} ErrorData;

extern void EmitErrorReport(void);
extern ErrorData *CopyErrorData(void);
extern void FreeErrorData(ErrorData *edata);
extern void FlushErrorState(void);
extern void ReThrowError(ErrorData *edata) pg_attribute_noreturn();
extern void ThrowErrorData(ErrorData *edata);
extern void pg_re_throw(void) pg_attribute_noreturn();

extern char *GetErrorContextStack(void);

/* 用于拦截消息的钩子，发送到服务器日志之前 */
typedef void (*emit_log_hook_type) (ErrorData *edata);
extern PGDLLIMPORT emit_log_hook_type emit_log_hook;


/* 可通过 GUC 配置的参数 */

typedef enum
{
	PGERROR_TERSE,				/* 单行错误消息 */
	PGERROR_DEFAULT,			/* 推荐的样式 */
	PGERROR_VERBOSE				/* 所有事实，女士 */
}			PGErrorVerbosity;

extern PGDLLIMPORT int Log_error_verbosity;
extern PGDLLIMPORT char *Log_line_prefix;
extern PGDLLIMPORT int Log_destination;
extern PGDLLIMPORT char *Log_destination_string;
extern PGDLLIMPORT bool syslog_sequence_numbers;
extern PGDLLIMPORT bool syslog_split_messages;

/* 日志目标位图 */
#define LOG_DESTINATION_STDERR	 1
#define LOG_DESTINATION_SYSLOG	 2
#define LOG_DESTINATION_EVENTLOG 4
#define LOG_DESTINATION_CSVLOG	 8
#define LOG_DESTINATION_JSONLOG	16
#ifdef FDD
//日志消息类型为审计日志
#define LOG_DESTINATION_AUDIT  	32
#endif

/* 其他导出的函数 */
extern void DebugFileOpen(void);
extern char *unpack_sql_state(int sql_state);
extern bool in_error_recursion_trouble(void);

/* 跨目标共享的常见函数 */
extern void reset_formatted_start_time(void);
extern char *get_formatted_start_time(void);
extern char *get_formatted_log_time(void);
extern const char *get_backend_type_for_log(void);
extern bool check_log_of_query(ErrorData *edata);
extern const char *error_severity(int elevel);
extern void write_pipe_chunks(char *data, int len, int dest);

/* 目标特定函数 */
extern void write_csvlog(ErrorData *edata);
extern void write_jsonlog(ErrorData *edata);

#ifdef HAVE_SYSLOG
extern void set_syslog_parameters(const char *ident, int facility);
#endif

/*
 * 将错误写入stderr（或在stderr不可用时以相同方式）。在使用
 * ereport/elog之前，必须安全地使用（内存上下文、GUC加载等）。
 */
extern void write_stderr(const char *fmt,...) pg_attribute_printf(1, 2);
#ifdef FDD
typedef void (*FDB_TYPE_WRITE_AUDIT_HOOK) (const char *data);
extern PGDLLIMPORT FDB_TYPE_WRITE_AUDIT_HOOK fdb_write_audit_hook;
void fdb_write_audit_log(char *data, int len);//将审计日志写入到pipe
#endif //FDD

/*
 * 使用仅具有异步信号安全的函数将消息写入 STDERR。这可以
 * 用于安全地从信号处理程序发出消息。
 */
extern void write_stderr_signal_safe(const char *fmt);

#endif							/* ELOG_H */
